#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#define NUM 1024
#define OP_NUM 64
#define NONE_REDIR   0
#define INPUT_REDIR  1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
//宏函数
#define trimSpace(start) do{\
                            while(*start == ' ') start++;\
}while(0)


char lineCommand[NUM];   //接收命令
char *op[OP_NUM];       //指针数组，存放拆分命令后的各字符串的指针
int lastCode = 0;       //记录最近一次进程的退出码和信号
int lastSignal = 0;

int redirType = NONE_REDIR; //重定向种类
char* redirFile = NULL; //重定向文件，默认为NULL

//检测命令是否存在重定向
void commandCheck(char* command)
{
    char* start=command;
    char* end=start+strlen(command);    //end指向\0
    
    while(start < end)
    {
        if(*start == '>')
        {  
            //输出重定向
            *start = 0;
            start++;
            redirType = OUTPUT_REDIR;
            if(*start == '>')   //追加重定向
            {
                *start = 0;
                start++;
                redirType = APPEND_REDIR;
            }

            trimSpace(start);
            redirFile = start;
        }
        else if(*start == '<')
        {
            *start = 0;     //把重定向符号置为0，就将command分开了，后面拆分时拆分的只是命令部分
            start++;        //satrt目前有可能指向空格，还要处理
            //调用宏函数
            trimSpace(start);
            redirType = INPUT_REDIR;
            redirFile = start;  //得到重定向文件
        }
        else
        {
            start++;
        }
    }

}




int main()
{
    while(1)
    {
        //每次开始前，都要初始化，否则第一次重定向后，每次都会进case
        redirFile = NULL;
        redirType = NONE_REDIR;

        printf("[用户名@主机名 当前路径]$ ");
        fflush(stdout);
    
        //获取用户输入
        char* s=fgets(lineCommand, sizeof(lineCommand)-1, stdin);
        assert(s != NULL);
        (void)s;
        //把我们在命令行输入的\n删掉
        lineCommand[strlen(lineCommand)-1] = 0;
        
        //检查输入的命令是否包含重定向
        commandCheck(lineCommand);
        //"ls -a -l >>   log.txt" -->  "ls -a -l "  "log.txt" 前边的是lineCommand，后边文件名利用redirFile字符串指针找到

        //拆分命令
        op[0]=strtok(lineCommand, " ");
        int i=1;
        //如果要执行ls，为其自动添加--color=auto选项
        if(op[0]!= NULL && strcmp(op[0],"ls")==0)
        {
            op[i++] = (char*)"--color=auto";
        }
        while(op[i++]=strtok(NULL, " "));
        
        //如果输入的是cd命令，即改变当前进程的工作目录，创建子进程，则改变了子进程的工作目录，子进程执行完，就结束了，无法改变父进程的
        //故不需要创建子进程，而是让shell执行，才能改变父进程的
        //这种不需要创建子进程，让shell自己执行对应的命令  这种叫做内置/内建命令
        if(op[0]!=NULL && strcmp(op[0],"cd")==0)
        {
            if(op[1] == NULL)   //cd  默认到当前用户的目录
            {
                chdir("/home/zwg");
            }
            else 
            {
                chdir(op[1]);
            }
            //shell执行完后，目录已经改变了，已经达到了我们输入cd  的目的，不需要再创建子进程了，直接输入下一条命令
            continue;
        }
        
        //echo
        if(op[0]!=NULL && strcmp(op[0],"echo")==0)
        {
            //经测试，只输入echo 会打印换行
            if(op[1]==NULL)
            {
                printf("\n");
            }
            else if(strcmp(op[1],"$?")==0)      //获取最近一次进程的退出码，这里又添加了信号
            {
                printf("%d, %d\n",lastCode, lastSignal);
            }
            else      //否则就输出我们输入的字符
            {
                printf("%s\n", op[1]);
            }

            continue;
        }

        

        //执行命令
        int id=fork();
        assert(id != -1);

        if(id==0)
        {
            switch(redirType)
            {
                case NONE_REDIR:
                    //没有重定向，啥也不干，正常进行替换
                    break;
                case INPUT_REDIR:
                    {
                        int fd = open(redirFile, O_RDONLY, 0666);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(6);
                        }
                        //输入重定向的文件已经成功打开
                        //下面进行输入重定向
                        dup2(fd, 0);
                    }
                     break;
                case OUTPUT_REDIR:
                case APPEND_REDIR:
                    {
                        int flags = O_WRONLY | O_CREAT;
                        if(redirType == APPEND_REDIR)
                            flags |= O_APPEND;
                        else
                            flags |= O_TRUNC;
                    
                        int fd = open(redirFile, flags, 0666);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(7);   
                        }
                        //输出/追加重定向的文件已经打开
                        //下面进行重定向
                        dup2(fd, 1);
                    }
                    break;
                default:
                    printf("bug\n");
                    break;
            }

            execvp(op[0], op);
            exit(6);                                                                                                                                                     
        }
        int status=0;
        pid_t ret=waitpid(id, &status, 0);
        assert(ret != -1);
        lastCode = (status>>8) & 0xff;
        lastSignal = status & 0x7f;
    }

    return 0;
}
